/*
 *  Author: Gary Kelly, gkelly2008@my.fit.edu
 *  Author: John Stevens, jstevens@fit.edu
 *  Class:  CSE 4232 - Network Programming
 *  Program: SQlite database synchronization client program
 */

#include <stdio.h>
#include <sqlite3.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <libtasn1.h>
#include <iostream>
#include <errno.h>
#include <list>
#include <sstream>
#include <vector>

using namespace std;

#define BUF_SIZE 512

char request[BUF_SIZE];
char response[BUF_SIZE];
int messageLength = BUF_SIZE;
int recvSize = BUF_SIZE;
int err = 0;
vector<string> rows;
vector<string> computers;
int localrowcount;
char *zErrMsg = 0;

//asn definitions
ASN1_TYPE def = ASN1_TYPE_EMPTY;
ASN1_TYPE req = ASN1_TYPE_EMPTY;
ASN1_TYPE resp = ASN1_TYPE_EMPTY;
char errorDesc[ASN1_MAX_ERROR_DESCRIPTION_SIZE];

//#include "database_asn1_tab.c"
const ASN1_ARRAY_TYPE database_asn1_tab[]={
  {"DatabaseMessagesModule",536875024,0},
  {0,1610612748,0},
  {"iso",1073741825,"1"},
  {"identified-organization",1073741825,"3"},
  {"dod",1073741825,"6"},
  {"internet",1073741825,"1"},
  {"security",1073741825,"5"},
  {"mechanisms",1073741825,"5"},
  {"sqliteserver",1,"108"},
  {"SyncRequest",1610612741},
  {"lastSnapshot",1082130449,0},
  {"tableNames",536870923,0},
  {0,2,"TableName"},
  {"Database",1610612741,0},
  {"tables",1610612747,0},
  {0,2,"Table"},
  {"name",1073741826,"DatabaseName"},
  {"snapshot",8388625,0},
  {"Table",1610612741,0},
  {"name",1073741826,"TableName"},
  {"fields",1610612747,0},
  {0,2,"FieldName"},
  {"fieldTypes",1610612747,0},
  {0,2,"FieldType"},
  {"rows",536870923,0},
  {0,2,"Row"},
  {"Row",1610612747,0},
  {0,2,"FieldValue"},
  {"FieldValue",1610612754,0},
  {"value",1073741831,0},
  {"empty",20,0},
  {"TableName",1610620930,"UTF8String"},
  {0,4104,"0"},
  {"DatabaseName",1610620930,"UTF8String"},
  {0,4104,"1"},
  {"FieldName",1610620930,"UTF8String"},
  {0,4104,"2"},
  {"FieldType",1610620930,"PrintableString"},
  {0,4104,"3"},
  {"UTF8String",1610620935,0},
  {0,4360,"12"},
  {"PrintableString",536879111,0},
  {0,4360,"19"},
  {0,0,0}
};

int clientSocket;	
struct sockaddr_in serverAddr;

void connectToServer(char * server);
void updateDatabase(int clientSocket);

static int callback(void *NotUsed, int argc, char **argv, char **azColName){
    int i;
    for(i=0; i<argc; i++){
    	//printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
     if(argv[i])
     {
     	
        computers.push_back(argv[i]);
     }          
    }

    return 0;
}

static int callback2(void *NotUsed, int argc, char **argv, char **azColName){
    int i;
    localrowcount = argc;
    localrowcount++;
    
    for(i=0; i<argc; i++){
      printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
     
     if(argv[i])
     {
     	string tmp (argv[i]);
     	rows.push_back(tmp);
     }
      
    }
    
    return 0;
}

//Parses and Connects to specified server 
void connectToServer(char* server)
{
	char * token;
	token = strtok (server,":");
	string tcpinfo[2];
	int index = 0;
	
	//Parses String and put
	//entries in an array
	while (token != NULL)
	{
		tcpinfo[index] = token;
		token = strtok (NULL, ":");
		index++;
	}
	
	//create clientSocket 
	if ( (clientSocket = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
		cerr << "socket error: " << strerror (errno);
	}
	
	//clear the socket (required to use it)
	memset (&serverAddr, 0, sizeof (serverAddr));
	//fill in the socket structure with the host's information
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = inet_addr(tcpinfo[0].c_str());
	serverAddr.sin_port = htons(atoi(tcpinfo[1].c_str()));
	
	//connect to the host
	if (connect (clientSocket, (struct sockaddr*) &serverAddr, sizeof (serverAddr)) == -1) {
		cerr << "connect error: " << strerror (errno) << endl;
	}
	else
	{
		updateDatabase(clientSocket);
	
		asn1_delete_structure (&def);
		asn1_delete_structure (&req);
		asn1_delete_structure (&resp);
	
		close(clientSocket);
	}
}

void updateDatabase(int clientSocket){

	//create the ASN1 structure
	if ( (err = asn1_array2tree (database_asn1_tab, &def, errorDesc)) != ASN1_SUCCESS) {
		cerr << "array2tree error: " << asn1_strerror (err);
	}

	//create a new request
	if ( (err = asn1_create_element (def, "DatabaseMessagesModule.SyncRequest", &req)) != ASN1_SUCCESS) {
		cerr << "create_element error: " << asn1_strerror (err);
	}

	//fill the time
	if ( (err = asn1_write_value (req, "lastSnapshot", "1201079363", 0)) != ASN1_SUCCESS) {
		cerr << "write_value lastSnapshot error: " << asn1_strerror (err);
	}

	//fill the table with names
	if ( (err = asn1_write_value (req, "tableNames", "NEW", 0)) != ASN1_SUCCESS) {
		cerr << "write_value tableNames New error: " << asn1_strerror (err);
	}
	
	if ( (err = asn1_write_value (req, "tableNames.?1", "peers", 0)) != ASN1_SUCCESS) {
        cerr << "write_value tableNames.?1 error: " << asn1_strerror (err);
    }
    
    //fill the table name with the user input
	if ( (err = asn1_write_value (req, "tableNames", "NEW", 0)) != ASN1_SUCCESS) {
		cerr << "write_value tableNames New error: " << asn1_strerror (err);
	}
	
	if ( (err = asn1_write_value (req, "tableNames.?2", "peers_addresses", 0)) != ASN1_SUCCESS) {
        cerr << "write_value tableNames.?1 error: " << asn1_strerror (err);
    }
    //asn1_print_structure(stdout,req,"",ASN1_PRINT_ALL);
    //encoding
    messageLength = sizeof(request);
	if ( (err = asn1_der_coding (req, "", request, &messageLength, errorDesc)) != ASN1_SUCCESS) {
        cerr << "der_coding error: " << asn1_strerror (err); 
    }

	//send data
    if (send (clientSocket, request, messageLength, 0) == -1) {
        cerr << "send error: " << errno;
    }
    
    //wait for response
    if ( (recvSize = (recv (clientSocket, response, BUF_SIZE, 0))) == -1) {
				cerr << "recv error: " << errno;	
	}
	
	//create a new request
	if ( (err = asn1_create_element (def, "DatabaseMessagesModule.Database", &resp)) != ASN1_SUCCESS) {
		cerr << "create_element error: " << asn1_strerror (err);
	}
	
	//decoding
	if ( (err = asn1_der_decoding (&resp, response, recvSize, errorDesc)) != ASN1_SUCCESS) {
        cerr << "der_decoding error: " << asn1_strerror (err);
    }
    
    //get tablename
    messageLength = BUF_SIZE;
    if ( (err = asn1_read_value (resp, "tables.?1.name", response, &messageLength)) != ASN1_SUCCESS) {
				cerr << "read_value error: " << asn1_strerror (err);
    }
    
    string tableone (response, messageLength);
    
    //get tablename
    messageLength = BUF_SIZE;
    if ( (err = asn1_read_value (resp, "tables.?2.name", response, &messageLength)) != ASN1_SUCCESS) {
				cerr << "read_value error: " << asn1_strerror (err);
    }
    
    string tabletwo (response, messageLength);
    
    //Fill list with global id from local database
    char *zErrMsg2 = 0;
    int rc = sqlite3_exec(db, "Select global_peer_ID from peers", callback2, 0, &zErrMsg2);
    if( rc!=SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg2);
      sqlite3_free(zErrMsg2);
    }
    
    //compare global id's in asn struct with current database (table doesn't matter same number of rows)
    int fieldCount = 0;
    bool rowexist;
    
	if ( (err = asn1_number_of_elements (resp, "tables.?1.rows", &fieldCount)) != ASN1_SUCCESS) {
		cerr << "number_of_elements error: " << asn1_strerror (err);
	}
			
    if(strncmp(tableone.c_str(),"peers",6) == 0)
    {
	    for (int index = 1; index <= fieldCount; index++)
		{
			messageLength = sizeof (response);
			ostringstream fieldValue;
			fieldValue << "tables.?1.rows.?" << index << ".?" << "1.value";
			if ( ( (err = asn1_read_value (resp, fieldValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
			       (err != ASN1_ELEMENT_NOT_FOUND)) {
				cerr << asn1_strerror (err);
				break;
			}
			
			string global_peer_id (response, messageLength);
			
			for(int index2 = 0; index2 < rows.size(); index2++)
			{				
				if(strncmp(rows.at(index2).c_str(),global_peer_id.c_str(),sizeof(global_peer_id.c_str())) != 0)
				{
					rowexist = false;
				}
				else
				{
					rowexist = true;
					break;
				}
			}
			
			if(!rowexist)
			{
				//update peers table
				
				localrowcount++;
				string query;
				stringstream count;
				count << localrowcount;
				query += "BEGIN; insert into peers (peer_ID, global_peer_ID, name, broadcastable, last_sync_date) values ('";
				query += count.str() + "','" + global_peer_id + "','";
				
				messageLength = sizeof (response);
				ostringstream nameValue;
				nameValue << "tables.?1.rows.?" << index << ".?" << "2.value";
				if ( ( (err = asn1_read_value (resp, nameValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				//CHECK FOR NUL NULL NULL NULL NULL
				string name (response,messageLength);
				query += name + "','";
				
				messageLength = sizeof (response);
				ostringstream brodValue;
				brodValue << "tables.?1.rows.?" << index << ".?" << "3.value";
				if ( ( (err = asn1_read_value (resp, brodValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				
				string brod (response,messageLength);
				query += brod + "','";
				
				messageLength = sizeof (response);
				ostringstream syncValue;
				syncValue << "tables.?1.rows.?" << index << ".?" << "4.value";
				if ( ( (err = asn1_read_value (resp, syncValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				
				string sync (response,messageLength);
				query += sync + "'); COMMIT;";
				cout << query << endl;
				int rc = sqlite3_exec (db,query.c_str (),NULL,NULL,&zErrMsg);	
				if( rc!=SQLITE_OK ){
			      fprintf(stderr, "SQL error: %s\n", zErrMsg);
			      sqlite3_free(zErrMsg);
			    }		
				
				//update peers_addresses table
				
				if ( (err = asn1_number_of_elements (resp, "tables.?2.rows", &fieldCount)) != ASN1_SUCCESS) {
					cerr << "number_of_elements error: " << asn1_strerror (err);
				}
				
				
				for(int index3 = 1; index3 <= fieldCount; index3++)
				{
					messageLength = sizeof (response);
					ostringstream rowValue;
					rowValue << "tables.?2.rows.?" << index3 << ".?" << "3.value";
					if ( ( (err = asn1_read_value (resp, rowValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
					       (err != ASN1_ELEMENT_NOT_FOUND)) {
						cerr << asn1_strerror (err);					
					}
					
					string global (response,messageLength);
					
					if(strncmp(global.c_str(),global_peer_id.c_str(),20) == 0)
					{					
					    query = "insert into peers_addresses (address,type,address_ID,peer_ID,date) values ('";			    
						messageLength = sizeof (response);
						ostringstream fieldValue;
						fieldValue << "tables.?2.rows.?" << index3 << ".?" << "1.value";
						if ( ( (err = asn1_read_value (resp, fieldValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
						       (err != ASN1_ELEMENT_NOT_FOUND)) {
							cerr << asn1_strerror (err);
							break;
						}
					
						string address (response,messageLength);
			
						query += address + "','";
						
						messageLength = sizeof (response);
						ostringstream typeValue;
						typeValue << "tables.?2.rows.?" << index3 << ".?" << "2.value";
						if ( ( (err = asn1_read_value (resp, typeValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
						       (err != ASN1_ELEMENT_NOT_FOUND)) {
							cerr << asn1_strerror (err);
							break;
						}
						//CHECK FOR NUL NULL NULL NULL NULL
						string type (response,messageLength);
						query += type + "','";
						query += count.str();
						query += "','" + count.str();
						query += "','";
						query += "1301079358')";							
						cout << query << endl;
						int rc2 = sqlite3_exec (db,query.c_str (),NULL,NULL,&zErrMsg);							
						if( rc2!=SQLITE_OK ){
					      fprintf(stderr, "SQL error: %s\n", zErrMsg);
					      sqlite3_free(zErrMsg);
					    }		
					}	
				}	
			}			
		}				
	}    
    else
    {
    	for (int index = 1; index <= fieldCount; index++)
		{
			messageLength = sizeof (response);
			ostringstream fieldValue;
			fieldValue << "tables.?2.rows.?" << index << ".?" << "1.value";
			if ( ( (err = asn1_read_value (resp, fieldValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
			       (err != ASN1_ELEMENT_NOT_FOUND)) {
				cerr << asn1_strerror (err);
				break;
			}
			
			string global_peer_id (response, messageLength);
			
			for(int index2 = 0; index2 < rows.size(); index2++)
			{				
				if(strncmp(rows.at(index2).c_str(),global_peer_id.c_str(),sizeof(global_peer_id.c_str())) != 0)
				{
					rowexist = false;
				}
				else
				{
					rowexist = true;
					break;
				}
			}
			
			if(!rowexist)
			{
				//update peers table
				
				localrowcount++;
				string query;
				stringstream count;
				count << localrowcount;
				query += "insert into peers (peer_ID, global_peer_ID, name, broadcastable, last_sync_date) values ('";
				query += count.str() + "','" + global_peer_id + "','";
				
				messageLength = sizeof (response);
				ostringstream nameValue;
				nameValue << "tables.?2.rows.?" << index << ".?" << "2.value";
				if ( ( (err = asn1_read_value (resp, nameValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				//CHECK FOR NUL NULL NULL NULL NULL
				string name (response,messageLength);
				query += name + "','";
				
				messageLength = sizeof (response);
				ostringstream brodValue;
				brodValue << "tables.?2.rows.?" << index << ".?" << "3.value";
				if ( ( (err = asn1_read_value (resp, brodValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				
				string brod (response,messageLength);
				query += brod + "','";
				
				messageLength = sizeof (response);
				ostringstream syncValue;
				syncValue << "tables.?2.rows.?" << index << ".?" << "4.value";
				if ( ( (err = asn1_read_value (resp, syncValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				
				string sync (response,messageLength);
				query += sync + "')";
				cout << query << endl;
				sqlite3_exec (db,query.c_str (),NULL,NULL,&zErrMsg);			
				
				//update peers_addresses table
				
				
			    query = "insert into peers_addresses (address,type,address_ID,peer_ID,date) values ('";			    
				messageLength = sizeof (response);
				ostringstream fieldValue;
				fieldValue << "tables.?1.rows.?" << index << ".?" << "1.value";
				if ( ( (err = asn1_read_value (resp, fieldValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
			
				string address (response,messageLength);
	
				query += address + "','";
				
				messageLength = sizeof (response);
				ostringstream typeValue;
				typeValue << "tables.?1.rows.?" << index << ".?" << "2.value";
				if ( ( (err = asn1_read_value (resp, nameValue.str ().c_str (), response, &messageLength)) != ASN1_SUCCESS) &&
				       (err != ASN1_ELEMENT_NOT_FOUND)) {
					cerr << asn1_strerror (err);
					break;
				}
				//CHECK FOR NUL NULL NULL NULL NULL
				string type (response,messageLength);
				query += type + "','";
				query += localrowcount + "','";
				query += "','" + localrowcount;
				query += "','";
				query += "1301079358')";							
				
				sqlite3_exec (db,query.c_str (),NULL,NULL,&zErrMsg);			
			}			
		}	
		
    }
}

void* clientRun(void *){        
    int rc;

    rc = sqlite3_exec(db, "Select address from peers_addresses", callback, 0, &zErrMsg);
    if( rc!=SQLITE_OK ){
      fprintf(stderr, "SQL error: %s\n", zErrMsg);
      sqlite3_free(zErrMsg);
    }
    
    for(unsigned int index = 0; index < computers.size(); index++)
	{
		cout << "Connecting to: " << computers[index] << endl;
			connectToServer(const_cast<char*> (computers[index].c_str()));
	}  
		
}
